#include "vm.h"
#include "mmu.h"
#include "arm.h"
#include "kalloc.h"
#include "console.h"
#include "memlayout.h"
#include "string.h"
#include "syscallno.h"
#include "list.h"

extern uint64_t kpgdir[];
extern char end[];

#include "test_list.inl"

void test_memory_map() {
    uint64_t n = PGSIZE / sizeof(uint64_t);

    /* prepare pgdir */
    uint64_t *vpgdir;
    if ((vpgdir = pgdir_init()) == 0)
        panic("test_memory_map: allocate base page directory.");

    /* map test region */
    uint64_t VRAM_START = 0x8040201000;
    uint64_t *va = (uint64_t*) VRAM_START;
    uint64_t *pa = kalloc();

    if (!pa)
        panic("test_memory_map: no physical page");

    // cprintf("allocated vpgdir=%p, pa=%p\n", vpgdir, pa);
    if (map_region(vpgdir, va, n * sizeof(uint64_t), IN_USPACE(pa), PTE_KERNEL | PTE_RW) != 0)
        panic("test_memory_map: map_region.");

    /* we need physical address of page directory */
    uint64_t *ppgdir = (uint64_t*) IN_USPACE(vpgdir);

    /* replace it! */
    cprintf("cpu %d: before: %p\n", arm_cpuid(), arm_get_ttbr0_el1());
    arm_set_ttbr0((uint64_t) ppgdir);
    cprintf("cpu %d: after:  %p\n", arm_cpuid(), arm_get_ttbr0_el1());

    /* read/write tests */
    for (uint64_t i = 0; i < n; i++) {
        uint64_t index = i + 1;
        uint64_t v0 = ((index * 0x19260817deadbeef) << 32) | index;

        uint64_t *vptr = va + i;
        uint64_t *pptr = pa + i;
        *vptr = v0;
        uint64_t v1 = *pptr;

        if (v0 != v1) {
            cprintf("i=%lld, v0=%llx, v1=%llx\n", i, v0, v1);
            panic("test_memory_map: read/write.");
        }

        // if ((i & 0x7F) == 0) {
        //     cprintf("vptr=%p, pptr=%p, v0=%llx\n", vptr, pptr, v0);
        // }
    }

    /* recover the original page directory */
    arm_set_ttbr0((uint64_t) IN_USPACE(kpgdir));
    // cprintf("cpu %d: current=%p\n", arm_cpuid(), arm_get_ttbr0_el1());

    // test vm_copy
    uint64_t *vpgdir2 = vm_copy(vpgdir, 0);
    assert(vpgdir2);
    // cprintf("vpgdir=%p, vpgdir2=%p\n", vpgdir, vpgdir2);

    uint64_t sum1 = 0, sum2 = 0;

    arm_set_ttbr0((uint64_t) IN_USPACE(vpgdir));
    for (uint64_t i = 0; i < n; i++) {
        uint64_t *vptr = va + i;
        sum1 += *vptr;
    }

    arm_set_ttbr0((uint64_t) IN_USPACE(vpgdir2));
    for (uint64_t i = 0; i < n; i++) {
        uint64_t *vptr = va + i;
        sum2 += *vptr;
    }

    arm_set_ttbr0((uint64_t) IN_USPACE(kpgdir));

    // cprintf("sum1=%llu, sum2=%llu\n", sum1, sum2);
    assert(sum1 == sum2);

    /* free the memory */
    vm_free(vpgdir, 0);
    vm_free(vpgdir2, 0);
}

static uint64_t volatile __cnt[ARM_NUM_CPU];

void test_interrupt() {
    arm_enable_interrupt();

    int i = arm_cpuid();
    register uint64_t x9 asm("x9");
    register uint64_t x10 asm("x10");
    register uint64_t x20 asm("x20");
    register uint64_t x21 asm("x21");
    x9 = x10 = x20 = x21 = __cnt[i] = 0;

    for ( ; __cnt[i] < 10; __cnt[i]++) {
        // cprintf("__cnt[%d] = %lld\n", arm_cpuid(), __cnt[i]);
        // asm volatile ("svc %0" :: "I" (SYS_hello));
        arm_svc(SYS_hello);
        if (x9 != __cnt[i] || x10 != __cnt[i] || x20 != __cnt[i] || x21 != __cnt[i])
            panic("test_interrupt");
        x9++;
        x10++;
        x20++;
        x21++;
    }

    arm_disable_interrupt();
}

static int __spinlock_nmax = 2000000;
static struct spinlock __spinlock;
static volatile uint64_t __spinlock_cnt;
static volatile int __spinlock_nstopped;

void test_spinlock() {
    cprintf("cpu %d: spinlock test begin.\n", arm_cpuid());

    arm_enable_interrupt();

    for (int i = 0; i < __spinlock_nmax; i++) {
        acquire(&__spinlock);
        __spinlock_cnt += 5;
        release(&__spinlock);
    }

    arm_disable_interrupt();

    cprintf("cpu %d: __spinlock_cnt=%llu\n", arm_cpuid(), __spinlock_cnt);

    acquire(&__spinlock);
    int v = ++__spinlock_nstopped;
    release(&__spinlock);

    if (v == ARM_NUM_CPU) {
        assert(__spinlock_cnt == 5 * __spinlock_nmax * ARM_NUM_CPU);
    }
}

void test_cprintf() {
    arm_enable_interrupt();

    int T = 20000;
    for (int i = 0; i < T; i++) {
        cprintf("                                      \r");
    }

    arm_disable_interrupt();
}

static struct spinlock __test_glock;
static volatile int __test_mmap_cnt;
static uint64_t __pagecnt;

void test_main() {
    cprintf("naïve test started at cpu %d.\n", arm_cpuid());

    acquire(&__test_glock);
    if (__pagecnt == 0)
        __pagecnt = kmem.page_count;
    release(&__test_glock);

    for (int repeat = 10; repeat > 0; repeat--) {
        test_memory_map();
    }

    acquire(&__test_glock);
    __test_mmap_cnt++;
    if (__test_mmap_cnt == ARM_NUM_CPU) {
        // release(&__test_glock);
        cprintf("__pagecnt=%llu, kmem.page_count=%llu\n",
            __pagecnt, kmem.page_count);
        assert(__pagecnt == kmem.page_count);
    }
    release(&__test_glock);

    test_list();

    test_interrupt();
    test_spinlock();
    test_cprintf();

    cprintf(
        "naïve test passed at cpu %d. page_count=%llu\n",
        arm_cpuid(), kmem.page_count);
}